package es

import (
	elastic "gopkg.in/olivere/elastic.v5"
	"fmt"
)

var operators = []string{"=", "!=", ">", ">=", "<", "<=", "like", "query_string", "exists", "script", "regexp", "wildcard"}

// 请求query
type Query struct {
	include         []string
	exclude         []string
	filter          []elastic.Query
	must            []elastic.Query
	mustNot         []elastic.Query
	should          []elastic.Query
	minShouldMatch  int
	source          interface{}
	collapse        string
	size            int
	from            int
	sorters         []elastic.Sorter
	suggester 		map[string]elastic.Suggester
	subAggregations map[string]elastic.Aggregation
	highlight       string
	scrollAlive     string
	scrollSize      int
	timeout         int
	profile         bool
}

// 不允许外部调用，仅允许 index.Query(xxx) 返回
func newQuery() *Query {
	return &Query{
		filter:          []elastic.Query{},
		must:            []elastic.Query{},
		mustNot:         []elastic.Query{},
		should:          []elastic.Query{},
		minShouldMatch:  1,
		size:            10,
		from:            0,
		sorters:         []elastic.Sorter{},
		subAggregations: map[string]elastic.Aggregation{},
		scrollAlive:     "5m",
		scrollSize:      1000,
	}
}

// 添加Filter条件
func (m *Model) AddFilter(elasticQuery elastic.Query) *Model {
	m.query.filter = append(m.query.filter, elasticQuery)
	return m
}

// 添加Must条件
func (m *Model) AddMust(elasticQuery elastic.Query) *Model {
	m.query.must = append(m.query.must, elasticQuery)
	return m
}

// 添加MustNot条件
func (m *Model) AddMustNot(elasticQuery elastic.Query) *Model {
	m.query.mustNot = append(m.query.mustNot, elasticQuery)
	return m
}

// 添加Should条件
func (m *Model) AddShould(elasticQuery elastic.Query) *Model {
	m.query.should = append(m.query.should, elasticQuery)
	return m
}

// MinimumShouldMatch 默认是1
func (m *Model) MinimumShouldMatch(minShouldMatch int) *Model {
	m.query.minShouldMatch = minShouldMatch
	return m
}

// when
func (m *Model) When(value bool, data interface{}, f func(m *Model, data interface{}) (*Model)) *Model {
	if (value) {
		m = f(m, data)
		return m
	}
	return m
}

// source
func (m *Model) Source(source interface{}) *Model {
	m.query.source = source
	return m
}

// profile
func (m *Model) Profile(profile bool) *Model {
	m.query.profile = profile
	return m
}

// 超时
func (m *Model) TimeOut(timeout int) *Model {
	m.query.timeout = timeout
	return m
}

// Include 指定字段
func (m *Model) Include(include ...string) *Model {
	m.query.include = include
	return m
}

// Exclude 排除字段
func (m *Model) Exclude(exclude ...string) *Model {
	m.query.exclude = exclude
	return m
}

// collapse 去重
func (m *Model) Collapse(collapse string) *Model {
	m.query.collapse = collapse
	return m
}

// sub aggregation 聚合
func (m *Model) SubAggregation(name string, subAggregation elastic.Aggregation) *Model {
	m.query.subAggregations[name] = subAggregation
	return m
}

// suggester 搜索建议
func (m *Model) Suggester(name string, suggester elastic.Suggester) *Model {
	m.query.suggester[name] = suggester
	return m
}

// highlight 高亮
func (m *Model) Highlight(highlight string) *Model {
	m.query.highlight = highlight
	return m
}

// format condition
func (m *Model) formatCondition() *elastic.SearchService {
	rawClient := m.client.rawClient
	search := rawClient.Search(m.indexName...).Query(m.getBoolQuery())

	// 设置请求资源
	if m.query.source != nil {
		search.Source(m.query.source)
	}

	// 设置profile
	if m.query.profile != false {
		search.Profile(m.query.profile)
	}

	// 超时设置
	if m.query.timeout != 0 {
		search.Timeout(fmt.Sprint("%ds", m.query.timeout))
	}

	// 指定字段
	if len(m.query.include) > 0 || len(m.query.exclude) > 0 {
		sc := elastic.NewFetchSourceContext(false)
		sc.SetFetchSource(true)
		sc.Include(m.query.include...)
		sc.Exclude(m.query.exclude...)
		search.FetchSourceContext(sc)
	}

	//排序
	if m.query.sorters != nil && len(m.query.sorters) > 0 {
		search.SortBy(m.query.sorters...)
	}

	// 去重
	if m.query.collapse != "" {
		search.Collapse(elastic.NewCollapseBuilder(m.query.collapse))
	}

	// 高亮
	if m.query.highlight != "" {
		search.Highlight(elastic.NewHighlight().Field(m.query.highlight))
	}

	// 聚合
	if len(m.query.subAggregations) > 0 {
		for k, v := range m.query.subAggregations {
			search.Aggregation(k, v)
		}
	}
	return search
}

// 依据字段将序排列
func (m *Model) Sort(fieldName string, sortType string) *Model {
	s := elastic.NewFieldSort(fieldName)
	if sortType == "asc" {
		s.Asc()
	}
	if sortType == "desc" {
		s.Desc()
	}
	m.query.sorters = append(m.query.sorters, s)
	return m
}

// Limit
func (m *Model) Limit(limit ...int) *Model {
	switch len(limit) {
	case 1:
		m.query.size = limit[0]
	case 2:
		m.query.from = limit[0]
		m.query.size = limit[1]
	}
	return m
}

// Take
func (m *Model) Take(limit ...int) *Model {
	switch len(limit) {
	case 1:
		m.query.size = limit[0]
	case 2:
		m.query.from = limit[0]
		m.query.size = limit[1]
	}
	return m
}

// 升序排序
func (m *Model) OrderAsc(fieldName string) *Model {
	s := elastic.NewFieldSort(fieldName).Asc()
	m.query.sorters = append(m.query.sorters, s)
	return m
}

// 降序排序
func (m *Model) OrderDesc(fieldName string) *Model {
	s := elastic.NewFieldSort(fieldName).Desc()
	m.query.sorters = append(m.query.sorters, s)
	return m
}

// 随机排序
func (m *Model) OrderRandom(fieldName string) *Model {
	s := elastic.NewScriptSort(elastic.NewScript("Math.random()"), "number")
	m.query.sorters = append(m.query.sorters, s)
	return m
}

// 设置分页 pageSize:每页数量，pageNum:页码,从1开始
func (m *Model) Page(pageNum int64, pageSize int64) *Model {
	if pageNum < 0 {
		pageNum = 0
	}
	if pageSize < 0 {
		pageSize = 0
	}
	m.query.size = int(pageSize)
	m.query.from = int((pageNum - 1) * pageSize)
	return m
}

// 获取boolquery
func (m *Model) getBoolQuery() *elastic.BoolQuery {
	query := elastic.NewBoolQuery()
	//filter
	if m.query.filter != nil && len(m.query.filter) > 0 {
		query.Filter(m.query.filter...)
	}
	//must
	if m.query.must != nil && len(m.query.must) > 0 {
		query.Must(m.query.must...)
	}
	//must not
	if m.query.mustNot != nil && len(m.query.mustNot) > 0 {
		query.MustNot(m.query.mustNot...)
	}
	//should
	if m.query.should != nil && len(m.query.should) > 0 {
		query.Should(m.query.should...)
		query.MinimumNumberShouldMatch(m.query.minShouldMatch)
	}
	return query
}
